﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CutAways.Illustration;
using Tao.OpenGl;

namespace CutAways.Geometry
{
	class Quad
	{
		private Vector[] points;

		public Vector[] Points
		{
			get { return points; }
			set { points = value; }
		}

		public Vector GetCenter()
		{
			return Vector.CenterOfLink(Vector.CenterOfLink(points[0], points[1]), Vector.CenterOfLink(points[2], points[3]));
		}

		public bool IsCollapsed()
		{
			return points[0] == points[1] || points[0] == points[3];
		}

		public Quad(Vector v1, Vector v2, Vector v3, Vector v4)
		{
			this.points = new Vector[4];
			points[0] = v1;
			points[1] = v2;
			points[2] = v3;
			points[3] = v4;
		}

		public Quad(Vector center, Vector normal, float radius)
		{
			this.points = new Vector[4];
			Vector x = Vector.SetLength(Vector.Cross(normal, Vector.YAxis/*new Vector(normal.X, normal.Y * 2f + 1f, normal.Z)*/), radius);
			Vector y = Vector.Rotate(x, (float)Math.PI * 0.5f, normal);
			points[0] = center - x - y;
			points[1] = center + x - y;
			points[2] = center + x + y;
			points[3] = center - x + y;
		}

		public Vector Normal()
		{
			return Vector.Cross(points[1] - points[0], points[3] - points[0]);
		}

		/// <summary>
		/// Zjisti zda tuto rovinu protina usecka ab
		/// </summary>
		/// <param name="a">prvni bod usecky</param>
		/// <param name="b">druhy bod usecky</param>
		/// <param name="intersection">prusecik primky ab s rovinou ctyruhelnika</param>
		/// <param name="parametres">vektor parametru udavajici, kde doslo k protnuti.
		/// prvni parametr(X) je parametr usecky. parametry Y a Z patri plose. pokud jsou vsechny v intervalu [0,1] tak doslo k protnuti uvnitr plochy a usecky.</param>
		/// <returns>true pokud doslo k protnuti roviny useckou, jinak false</returns>
		public bool IntersectsWithLine(Vector a, Vector b, out Vector intersection, out Vector parametres)
		{
			if (!tryComputeParametresOfIntersectionWithLine(a, b, out parametres))
			{
				intersection = new Vector();
				return false;
			}
			intersection = a + (b - a) * parametres.X;
			return parametres.X >= 0f && parametres.X <= 1f && parametres.Y >= 0f && parametres.Y <= 1f && parametres.Z >= 0f && parametres.Z <= 1f;
		}

		private bool tryComputeParametresOfIntersectionWithLine(Vector a, Vector b, out Vector parametres)
		{
			Matrix3x3 set = new Matrix3x3();
			Vector v0 = points[0];
			Vector v1 = points[1];
			Vector v2 = points[3];

			set[0, 0] = a.X - b.X;
			set[0, 1] = v1.X - v0.X;
			set[0, 2] = v2.X - v0.X;
			set[1, 0] = a.Y - b.Y;
			set[1, 1] = v1.Y - v0.Y;
			set[1, 2] = v2.Y - v0.Y;
			set[2, 0] = a.Z - b.Z;
			set[2, 1] = v1.Z - v0.Z;
			set[2, 2] = v2.Z - v0.Z;

			Matrix3x3 inverted;
			if (!set.TryInvert(out inverted))
			{
				parametres = new Vector();
				return false;
			}

			parametres = inverted * new Vector(a.X - v0.X, a.Y - v0.Y, a.Z - v0.Z);
			return true;
		}

		/// <summary>
		/// Spocita parametry prekryti objektu promitnuteho na tuto rovinu.
		/// </summary>
		public void IntersectionWith(OrientedBoundingBox obb, Vector eyePos, out Interval u, out Interval v)
		{
			u = v = Interval.Empty;
			// spocitam pruseciky se vsemi useckami
			Vector?[] pointsOnPlane = new Vector?[12];

			//foreach (Line line in (from p in obb.Points select new Line(p, eyePos)))
			for (int i = 0; i < obb.Points.Length; i++)
			{
				Vector intersection, parametres;
				IntersectsWithLine(obb.Points[i], eyePos, out intersection, out parametres);
				if (parametres.X < 0f || parametres.X > 1f) // usecka neprotla plochu, tak na to seru - a to delas velkou chybu !!!
					pointsOnPlane[i] = null;
				else
					pointsOnPlane[i] = new Vector(parametres.Y, parametres.Z, 0);
			}

			// vytvorim usecky v rovine
			List<Line> planeLines = new List<Line>();

			if (pointsOnPlane[0].HasValue && pointsOnPlane[1].HasValue)
				planeLines.Add(new Line(pointsOnPlane[0].Value, pointsOnPlane[1].Value));
			if (pointsOnPlane[1].HasValue && pointsOnPlane[5].HasValue)
				planeLines.Add(new Line(pointsOnPlane[1].Value, pointsOnPlane[5].Value));
			if (pointsOnPlane[5].HasValue && pointsOnPlane[4].HasValue)
				planeLines.Add(new Line(pointsOnPlane[5].Value, pointsOnPlane[4].Value));
			if (pointsOnPlane[4].HasValue && pointsOnPlane[0].HasValue)
				planeLines.Add(new Line(pointsOnPlane[4].Value, pointsOnPlane[0].Value));
			if (pointsOnPlane[0].HasValue && pointsOnPlane[2].HasValue)
				planeLines.Add(new Line(pointsOnPlane[0].Value, pointsOnPlane[2].Value));
			if (pointsOnPlane[1].HasValue && pointsOnPlane[3].HasValue)
				planeLines.Add(new Line(pointsOnPlane[1].Value, pointsOnPlane[3].Value));
			if (pointsOnPlane[5].HasValue && pointsOnPlane[7].HasValue)
				planeLines.Add(new Line(pointsOnPlane[5].Value, pointsOnPlane[7].Value));
			if (pointsOnPlane[4].HasValue && pointsOnPlane[6].HasValue)
				planeLines.Add(new Line(pointsOnPlane[4].Value, pointsOnPlane[6].Value));
			if (pointsOnPlane[2].HasValue && pointsOnPlane[3].HasValue)
				planeLines.Add(new Line(pointsOnPlane[2].Value, pointsOnPlane[3].Value));
			if (pointsOnPlane[3].HasValue && pointsOnPlane[7].HasValue)
				planeLines.Add(new Line(pointsOnPlane[3].Value, pointsOnPlane[7].Value));
			if (pointsOnPlane[7].HasValue && pointsOnPlane[6].HasValue)
				planeLines.Add(new Line(pointsOnPlane[7].Value, pointsOnPlane[6].Value));
			if (pointsOnPlane[6].HasValue && pointsOnPlane[2].HasValue)
				planeLines.Add(new Line(pointsOnPlane[6].Value, pointsOnPlane[2].Value));

			// pro kazdou usecku promitnutou na plochu spocitam pruseciky s ohranicujicimi primkami quadu
			
			Line ab = new Line(new Vector(0f, 0f, 0f), new Vector(1f, 0f, 0f));
			Line bc = new Line(new Vector(1f, 0f, 0f), new Vector(1f, 1f, 0f));
			Line cd = new Line(new Vector(1f, 1f, 0f), new Vector(0f, 1f, 0f));
			Line da = new Line(new Vector(0f, 1f, 0f), new Vector(0f, 0f, 0f));
			
			foreach (Line line in planeLines)
			{
				// pokud je nektery z bodu usecky uvnitr obdelnika tak ho pridat do parametru
				if (Interval.ZeroOne.Contains(line.A.X) && Interval.ZeroOne.Contains(line.A.Y))
				{
					u.Join(line.A.X);
					v.Join(line.A.Y);
				}
				if (Interval.ZeroOne.Contains(line.B.X) && Interval.ZeroOne.Contains(line.B.Y))
				{
					u.Join(line.B.X);
					v.Join(line.B.Y);
				}

				// pridat pruseciky usecek s hranami obdelnika

				Vector intersection;
				float rayParam;
				if (ab.IntersectsLineWithLineSegment(line, out intersection, out rayParam))
				{
					u.Join(intersection.X);
					if (Interval.ZeroOne.Contains(rayParam))
						v.Join(intersection.Y);
				}
				if (bc.IntersectsLineWithLineSegment(line, out intersection, out rayParam))
				{
					if (Interval.ZeroOne.Contains(rayParam))
						u.Join(intersection.X);
					v.Join(intersection.Y);
				}
				if (cd.IntersectsLineWithLineSegment(line, out intersection, out rayParam))
				{
					u.Join(intersection.X);
					if (Interval.ZeroOne.Contains(rayParam))
						v.Join(intersection.Y);
				}
				if (da.IntersectsLineWithLineSegment(line, out intersection, out rayParam))
				{
					if (Interval.ZeroOne.Contains(rayParam))
						u.Join(intersection.X);
					v.Join(intersection.Y);
				}

			}
		}

		public void Draw()
		{
			Gl.glDisable(Gl.GL_LIGHTING);
			Gl.glBegin(Gl.GL_LINES);
			{
				Gl.glVertex3f(points[0].X, points[0].Y, points[0].Z);
				Gl.glVertex3f(points[1].X, points[1].Y, points[1].Z);

				Gl.glVertex3f(points[1].X, points[1].Y, points[1].Z);
				Gl.glVertex3f(points[2].X, points[2].Y, points[2].Z);

				Gl.glVertex3f(points[2].X, points[2].Y, points[2].Z);
				Gl.glVertex3f(points[3].X, points[3].Y, points[3].Z);

				Gl.glVertex3f(points[3].X, points[3].Y, points[3].Z);
				Gl.glVertex3f(points[0].X, points[0].Y, points[0].Z);
			}
			Gl.glEnd();
			Gl.glEnable(Gl.GL_LIGHTING);
		}
	}
}
